﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Tao.OpenGl;
using SceneLib;
using Tao.FreeGlut;
using System.Drawing;

namespace Tarea2Raytracing
{
    class RaytraceRenderer
    {

        private Scene scene;
        private int width;
        private int height;

        private bool isRaytracing;

        private Vector[,] buffer;
        private int y = 0, x = 0;

        private int imageIndex = 0;

        public bool Shading { get; set; }
        public bool Antialias { get; set; }

        public RaytraceRenderer(Scene scene, int width, int height)
        {
            this.scene = scene;
            this.width = width;
            this.height = height;
            this.buffer = new Vector[width, height];
        }

        public void ResetTracer()
        {
            x = 0;
            y = 0;
            isRaytracing = true;
            CalculateUVW();
        }

        public void Update()
        {
            if (isRaytracing)
            {                
                buffer[x, y] = CalculatePixel(x, y);
                x++;
                if (x >= width)
                {
                    x = 0;
                    y++;
                    Glut.glutPostRedisplay();
                }

                if (y >= height)
                {
                    isRaytracing = false;
                    Glut.glutPostRedisplay();
                    SaveImage(buffer, "image" + imageIndex + ".png");
                    imageIndex++;
                }
            }
        }

        public void Render()
        {
            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
            Gl.glOrtho(0, width, 0, height, 1, -1);

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            Gl.glClearColor(0, 0, 0, 0);

            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT);
            Gl.glDisable(Gl.GL_DEPTH_TEST);
            Gl.glEnable(Gl.GL_BLEND);
            Gl.glBlendFunc(Gl.GL_SRC_ALPHA, Gl.GL_ONE_MINUS_SRC_ALPHA);

            Gl.glBegin(Gl.GL_POINTS);
            
            for (int i = 0; i < width; i++)
            {

                for (int j = 0; j < height; j++)
                {
                    if (buffer[i, j] == null)
                        DrawPixel(new Vector(i, j), new Vector(0, 0, 0));
                    else
                        DrawPixel(new Vector(i, j), buffer[i, j]);

                }

            }

            Gl.glEnd();
            Gl.glFlush();
        }

        private void DrawPixel(Vector position, Vector color)
        {
            Gl.glColor4f(color.x, color.y, color.z, color.w);
            Gl.glVertex2i((int)position.x, (int)position.y);
        }

        public Vector CalculatePixel(int x, int y)
        {
            Vector e = scene.Camera.Position;
            Vector u = scene.Camera.U;
            Vector v = scene.Camera.V;
            Vector w = scene.Camera.W;
            float nearClip = scene.Camera.NearClip;
            float top = Convert.ToSingle(Math.Tan(scene.Camera.FieldOfView * Math.PI / 360)) * scene.Camera.NearClip;
            float right = top * width / height;
            float button = -top;
            float left = -right;
            Random rand = new Random();
            int n = 2;
            int m = 1;
            //int n = 4;
            //int m = 8;          
            Vector cMotion = new Vector();
            float time0 = 0;
            float range = 1.0f / m;

            for (int k = 1; k <= m; k++)
            {
                Vector color = new Vector();
                time0 = (k - 1) * range;
                float timef = time0 + range * Convert.ToSingle(rand.NextDouble());
                for (int p = 0; p < n; p++)
                    for (int q = 0; q < n; q++)
                    {
                        float delta = Convert.ToSingle(rand.NextDouble());

                        float ux = left + (x + (p + delta) / n) * (right - left) / width;
                        float uy = button + (y + (q + delta) / n) * (top - button) / height;
                        Vector s = e + ux * u + uy * v - nearClip * w;

                        color = color + RayColor(e, s - e, 0, Single.PositiveInfinity, 4, time0, timef);                        
                    }

                color = (1.0f / (n * n)) * color;
                color.Clamp3();                
                cMotion = cMotion + color;
            }

            cMotion = (1.0f / m) * cMotion;
            return cMotion;
        }

        private Vector RayColor(Vector po, Vector d, float to, float tf, int reflectionLevel, float time0, float timef)
        {
            Intersection iMin = Intersection.NOINTERSECT;

            foreach (SceneObject aObject in scene.Objects)
            {
                Intersection i = aObject.Intersect(po, d, to, tf, time0, timef);

                if (i.T < iMin.T)
                {
                    iMin = i;
                }
            }

            if (iMin.IsIntersected)
                return GetColor(iMin, d, reflectionLevel, time0, timef);
            else
                return scene.Background.Color;
        }

        private void CalculateUVW()
        {
            Vector w = scene.Camera.Target - scene.Camera.Position;
            scene.Camera.W = -1 * w.Normalize3();
            scene.Camera.U = Vector.Cross3(scene.Camera.Up, scene.Camera.W);
            scene.Camera.U.Normalize3();
            scene.Camera.V = Vector.Cross3(scene.Camera.W, scene.Camera.U);
            scene.Camera.V.Normalize3();
        }

        private Vector GetColor(Intersection intersection, Vector d, int reflectionLevel, float time0, float timef)
        {
            Vector finalColor = GetAmbientColor(intersection);

            finalColor.Clamp3();
            
            foreach (SceneLight light in scene.Lights)
            {
                Vector lightColor = new Vector();
                Vector l = light.Position - intersection.P;                
                
                l.Normalize3();

                if (!TestShadow(intersection.P, light.Position, time0, timef))
                {
                    float distance = (light.Position - intersection.P).Magnitude3();

                    lightColor = light.Color * 1 / (light.AtenuationQuadratic * distance * distance + light.AtenuationLinear * distance + light.AtenuationConstant);
                    finalColor = finalColor + GetDiffuseColor(intersection, l, lightColor) + GetPhongColor(intersection, l, lightColor);                    
                    finalColor.Clamp3();                   
                }                
            }
            
            if (intersection.Material.Reflective != null && reflectionLevel > 0)
            {
                Vector r = d - 2 * Vector.Dot3(d, intersection.Normal) * intersection.Normal;

                finalColor = finalColor + intersection.Material.Reflective * RayColor(intersection.P, r, 0.05f, Single.PositiveInfinity, 
                                                        reflectionLevel - 1, time0, timef);
                finalColor.Clamp3();
            }
                               
            finalColor = finalColor + TestRefraction(intersection, d, reflectionLevel, time0, timef);            
            finalColor.Clamp3();

            return finalColor;
        }

        private Vector GetDiffuseColor(Intersection intersection, Vector l, Vector lightColor)
        {            
            Vector color = new Vector(0, 0, 0);

            color = color + lightColor * intersection.Material.Diffuse * intersection.ObjectScene.GetTextureColor(intersection.P) * Math.Max(0, Vector.Dot3(intersection.Normal, l));
            color.Clamp3();

            return color;
        }

        private Vector GetAmbientColor(Intersection intersection)
        {
            Vector color = intersection.Material.Diffuse * intersection.ObjectScene.GetTextureColor(intersection.P) * scene.Background.AmbientLight;
            color.Clamp3();

            return color;
        }

        private Vector GetPhongColor(Intersection intersection, Vector l, Vector lightColor)
        {
            Vector e = scene.Camera.Position - intersection.P;
            Vector color = new Vector(0, 0, 0);

            e.Normalize3();
            
            Vector h = e + l;

            h.Normalize3();
            color = color + lightColor * intersection.Material.Specular * Convert.ToSingle(Math.Pow(Vector.Dot3(h, intersection.Normal), 
                                                                                                    Math.Round(intersection.Material.Shininess, 2)));
            color.Clamp3();

            return color;
        }

        private bool TestShadow(Vector p, Vector f, float time0, float timef)
        {
            foreach (SceneObject aObject in scene.Objects)
            {
                Intersection i = aObject.Intersect(p, f - p, 0.05f, 1, time0, timef);

                if (i.IsIntersected && i.Material.RefractionIndex == null)
                    return true;
            }

            return false;
        }

        private Vector TestRefraction(Intersection intersection, Vector direction, int reflectionLevel, float time0, float timef)
        {
            if (intersection.Material.RefractionIndex != null)
            {
                float n = intersection.Material.RefractionIndex.x;
                Vector t;
                Vector d = direction.Copy();
                Vector normal = intersection.Normal.Copy();
                Vector p = intersection.P;

                d.Normalize3();
                normal.Normalize3();
                t = Refract(d, normal, 1, n);

                if (t != null)
                {
                    Intersection myIntersection = intersection.ObjectScene.Intersect(p, t, 0.05f, Single.PositiveInfinity, time0, timef);

                    if (myIntersection.IsIntersected)
                    {
                        d = t.Copy();
                        normal = myIntersection.Normal.Copy();
                        d.Normalize3();
                        normal.Normalize3();
                        t = Refract(d, normal, n, 1);

                        if (t == null)
                            t = d - 2 * Vector.Dot3(d, normal) * normal;
                        p = myIntersection.P;
                    }
                }
                else
                    return new Vector();

                return intersection.Material.Transparent * RayColor(p, t, 0.05f, Single.PositiveInfinity, reflectionLevel, time0, timef);
            }

            return new Vector();
        }
    
        private Vector Refract(Vector d, Vector n, float n1, float n2)
        {
            float discriminant = 1 - n1 * n1 * (1 - Vector.Dot3(d, n) * Vector.Dot3(d, n)) / (n2 * n2);

            if (discriminant < 0)
                return null;
            return (n1 / n2) * (d - Vector.Dot3(d, n) * n) - Convert.ToSingle(Math.Sqrt(discriminant)) * n;
        }

        private void SaveImage(Vector[,] buffer, string fileName)
        {
            Bitmap image = new Bitmap(buffer.GetLength(0), buffer.GetLength(1));

            for (int i = 0; i < buffer.GetLength(0); i++)
            {
                for (int j = 0; j < buffer.GetLength(1); j++)
                {
                    int r = (int)(255 * buffer[i, j].x);
                    int g = (int)(255 * buffer[i, j].y);
                    int b = (int)(255 * buffer[i, j].z);
                    image.SetPixel(i, buffer.GetLength(1) - 1 - j, Color.FromArgb(r, g, b));
                }
            }
            image.Save(fileName);
            return;
        }
    }
}
